// main.cpp
#include <QApplication>
#include <QMainWindow>
#include <QWidget>
#include <QPainter>
#include <QKeyEvent>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QMessageBox>
#include <QTimer>
#include <QTime>
#include <QRandomGenerator>

class TetrisBlock {
public:
    enum Shape { NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape };

    TetrisBlock() { setShape(NoShape); }

    void setShape(Shape shape) {
        static const int coordsTable[8][4][2] = {
            { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
            { { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
            { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
            { { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
            { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
            { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
            { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
            { { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
        };

        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 2; ++j)
                coords[i][j] = coordsTable[shape][i][j];
        }
        blockShape = shape;
    }

    Shape shape() const { return blockShape; }
    int x(int index) const { return coords[index][0]; }
    int y(int index) const { return coords[index][1]; }
    int minX() const {
        int min = coords[0][0];
        for (int i = 1; i < 4; ++i)
            min = qMin(min, coords[i][0]);
        return min;
    }
    int maxX() const {
        int max = coords[0][0];
        for (int i = 1; i < 4; ++i)
            max = qMax(max, coords[i][0]);
        return max;
    }
    int minY() const {
        int min = coords[0][1];
        for (int i = 1; i < 4; ++i)
            min = qMin(min, coords[i][1]);
        return min;
    }
    int maxY() const {
        int max = coords[0][1];
        for (int i = 1; i < 4; ++i)
            max = qMax(max, coords[i][1]);
        return max;
    }
    TetrisBlock rotatedLeft() const {
        if (blockShape == SquareShape)
            return *this;

        TetrisBlock result;
        result.blockShape = blockShape;
        for (int i = 0; i < 4; ++i) {
            result.coords[i][0] = y(i);
            result.coords[i][1] = -x(i);
        }
        return result;
    }
    TetrisBlock rotatedRight() const {
        if (blockShape == SquareShape)
            return *this;

        TetrisBlock result;
        result.blockShape = blockShape;
        for (int i = 0; i < 4; ++i) {
            result.coords[i][0] = -y(i);
            result.coords[i][1] = x(i);
        }
        return result;
    }

private:
    Shape blockShape;
    int coords[4][2];
};

class TetrisBoard : public QWidget {
    Q_OBJECT

public:
    TetrisBoard(QWidget *parent = nullptr);

    void start();
    void pause();
    void setNextPieceLabel(QLabel *label) { nextPieceLabel = label; }

public slots:
    void gameOver();

signals:
    void scoreChanged(int score);
    void levelChanged(int level);
    void linesRemovedChanged(int numLines);

protected:
    void paintEvent(QPaintEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;
    void timerEvent(QTimerEvent *event) override;

private:
    enum { BoardWidth = 10, BoardHeight = 22 };

    TetrisBlock &shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }
    int timeoutTime() { return 1000 / (1 + level); }
    int squareWidth() { return contentsRect().width() / BoardWidth; }
    int squareHeight() { return contentsRect().height() / BoardHeight; }
    void clearBoard();
    void dropDown();
    void oneLineDown();
    void pieceDropped();
    void removeFullLines();
    void newPiece();
    void showNextPiece();
    bool tryMove(const TetrisBlock &newPiece, int newX, int newY);
    void drawSquare(QPainter &painter, int x, int y, TetrisBlock::Shape shape);

    QBasicTimer timer;
    QLabel *nextPieceLabel;
    bool isStarted;
    bool isPaused;
    bool isWaitingAfterLine;
    TetrisBlock curPiece;
    TetrisBlock nextPiece;
    int curX;
    int curY;
    int numLinesRemoved;
    int numPiecesDropped;
    int score;
    int level;
    TetrisBlock board[BoardWidth * BoardHeight];
};

TetrisBoard::TetrisBoard(QWidget *parent)
    : QWidget(parent), nextPieceLabel(nullptr), isStarted(false), isPaused(false),
      isWaitingAfterLine(false), numLinesRemoved(0), numPiecesDropped(0),
      score(0), level(1) {
    setFocusPolicy(Qt::StrongFocus);
    clearBoard();
    nextPiece.setShape(TetrisBlock::NoShape);
}

void TetrisBoard::start() {
    if (isPaused)
        return;

    isStarted = true;
    isWaitingAfterLine = false;
    numLinesRemoved = 0;
    numPiecesDropped = 0;
    score = 0;
    level = 1;
    clearBoard();

    emit scoreChanged(score);
    emit levelChanged(level);
    emit linesRemovedChanged(numLinesRemoved);

    newPiece();
    timer.start(timeoutTime(), this);
}

void TetrisBoard::pause() {
    if (!isStarted)
        return;

    isPaused = !isPaused;
    if (isPaused) {
        timer.stop();
    } else {
        timer.start(timeoutTime(), this);
    }
    update();
}

void TetrisBoard::gameOver() {
    timer.stop();
    isStarted = false;
    QMessageBox::information(this, tr("游戏结束"), 
        tr("你的得分: %1").arg(score));
}

void TetrisBoard::paintEvent(QPaintEvent *event) {
    Q_UNUSED(event);

    QPainter painter(this);
    QRect rect = contentsRect();

    if (isPaused) {
        painter.drawText(rect, Qt::AlignCenter, tr("游戏暂停"));
        return;
    }

    int boardTop = rect.bottom() - BoardHeight * squareHeight();

    for (int i = 0; i < BoardHeight; ++i) {
        for (int j = 0; j < BoardWidth; ++j) {
            TetrisBlock::Shape shape = shapeAt(j, BoardHeight - i - 1).shape();
            if (shape != TetrisBlock::NoShape)
                drawSquare(painter, rect.left() + j * squareWidth(),
                          boardTop + i * squareHeight(), shape);
        }
    }

    if (curPiece.shape() != TetrisBlock::NoShape) {
        for (int i = 0; i < 4; ++i) {
            int x = curX + curPiece.x(i);
            int y = curY - curPiece.y(i);
            drawSquare(painter, rect.left() + x * squareWidth(),
                      boardTop + (BoardHeight - y - 1) * squareHeight(),
                      curPiece.shape());
        }
    }
}

void TetrisBoard::keyPressEvent(QKeyEvent *event) {
    if (!isStarted || isPaused || curPiece.shape() == TetrisBlock::NoShape) {
        QWidget::keyPressEvent(event);
        return;
    }

    switch (event->key()) {
    case Qt::Key_Left:
        tryMove(curPiece, curX - 1, curY);
        break;
    case Qt::Key_Right:
        tryMove(curPiece, curX + 1, curY);
        break;
    case Qt::Key_Down:
        tryMove(curPiece.rotatedRight(), curX, curY);
        break;
    case Qt::Key_Up:
        tryMove(curPiece.rotatedLeft(), curX, curY);
        break;
    case Qt::Key_Space:
        dropDown();
        break;
    case Qt::Key_D:
        oneLineDown();
        break;
    case Qt::Key_P:
        pause();
        break;
    default:
        QWidget::keyPressEvent(event);
    }
}

void TetrisBoard::timerEvent(QTimerEvent *event) {
    if (event->timerId() == timer.timerId()) {
        if (isWaitingAfterLine) {
            isWaitingAfterLine = false;
            newPiece();
            timer.start(timeoutTime(), this);
        } else {
            oneLineDown();
        }
    } else {
        QWidget::timerEvent(event);
    }
}

void TetrisBoard::clearBoard() {
    for (int i = 0; i < BoardHeight * BoardWidth; ++i)
        board[i].setShape(TetrisBlock::NoShape);
}

void TetrisBoard::dropDown() {
    int dropHeight = 0;
    int newY = curY;
    while (newY > 0) {
        if (!tryMove(curPiece, curX, newY - 1))
            break;
        --newY;
        ++dropHeight;
    }
    pieceDropped();
    score += dropHeight * 2;
    emit scoreChanged(score);
}

void TetrisBoard::oneLineDown() {
    if (!tryMove(curPiece, curX, curY - 1))
        pieceDropped();
}

void TetrisBoard::pieceDropped() {
    for (int i = 0; i < 4; ++i) {
        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);
        shapeAt(x, y).setShape(curPiece.shape());
    }

    ++numPiecesDropped;
    if (numPiecesDropped % 25 == 0) {
        ++level;
        timer.start(timeoutTime(), this);
        emit levelChanged(level);
    }

    removeFullLines();
    if (!isWaitingAfterLine)
        newPiece();
}

void TetrisBoard::removeFullLines() {
    int numFullLines = 0;

    for (int i = BoardHeight - 1; i >= 0; --i) {
        bool lineIsFull = true;

        for (int j = 0; j < BoardWidth; ++j) {
            if (shapeAt(j, i).shape() == TetrisBlock::NoShape) {
                lineIsFull = false;
                break;
            }
        }

        if (lineIsFull) {
            ++numFullLines;
            for (int k = i; k < BoardHeight - 1; ++k) {
                for (int j = 0; j < BoardWidth; ++j)
                    shapeAt(j, k).setShape(shapeAt(j, k + 1).shape());
            }
            for (int j = 0; j < BoardWidth; ++j)
                shapeAt(j, BoardHeight - 1).setShape(TetrisBlock::NoShape);
        }
    }

    if (numFullLines > 0) {
        numLinesRemoved += numFullLines;
        score += 10 * numFullLines * numFullLines;
        emit linesRemovedChanged(numLinesRemoved);
        emit scoreChanged(score);
        isWaitingAfterLine = true;
        curPiece.setShape(TetrisBlock::NoShape);
        update();
    }
}

void TetrisBoard::newPiece() {
    curPiece = nextPiece;
    nextPiece.setShape(TetrisBlock::Shape(QRandomGenerator::global()->bounded(7) + 1));
    showNextPiece();
    curX = BoardWidth / 2 + 1;
    curY = BoardHeight - 1 + curPiece.minY();

    if (!tryMove(curPiece, curX, curY)) {
        curPiece.setShape(TetrisBlock::NoShape);
        timer.stop();
        isStarted = false;
        gameOver();
    }
}

void TetrisBoard::showNextPiece() {
    if (!nextPieceLabel)
        return;

    int dx = nextPiece.maxX() - nextPiece.minX() + 1;
    int dy = nextPiece.maxY() - nextPiece.minY() + 1;

    QPixmap pixmap(dx * squareWidth(), dy * squareHeight());
    QPainter painter(&pixmap);
    painter.fillRect(pixmap.rect(), nextPieceLabel->palette().window());

    for (int i = 0; i < 4; ++i) {
        int x = nextPiece.x(i) - nextPiece.minX();
        int y = nextPiece.y(i) - nextPiece.minY();
        drawSquare(painter, x * squareWidth(), y * squareHeight(),
                  nextPiece.shape());
    }
    nextPieceLabel->setPixmap(pixmap);
}

bool TetrisBoard::tryMove(const TetrisBlock &newPiece, int newX, int newY) {
    for (int i = 0; i < 4; ++i) {
        int x = newX + newPiece.x(i);
        int y = newY - newPiece.y(i);
        if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
            return false;
        if (shapeAt(x, y).shape() != TetrisBlock::NoShape)
            return false;
    }

    curPiece = newPiece;
    curX = newX;
    curY = newY;
    update();
    return true;
}

void TetrisBoard::drawSquare(QPainter &painter, int x, int y, TetrisBlock::Shape shape) {
    static const QRgb colorTable[8] = {
        0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
        0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00
    };

    QColor color = colorTable[int(shape)];
    painter.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2, color);

    painter.setPen(color.lighter());
    painter.drawLine(x, y + squareHeight() - 1, x, y);
    painter.drawLine(x, y, x + squareWidth() - 1, y);

    painter.setPen(color.darker());
    painter.drawLine(x + 1, y + squareHeight() - 1,
                    x + squareWidth() - 1, y + squareHeight() - 1);
    painter.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
                    x + squareWidth() - 1, y + 1);
}

class TetrisWindow : public QMainWindow {
    Q_OBJECT

public:
    TetrisWindow();

private slots:
    void startGame();
    void pauseGame();
    void updateScore(int score);
    void updateLevel(int level);
    void updateLines(int lines);

private:
    TetrisBoard *board;
    QLabel *scoreLabel;
    QLabel *levelLabel;
    QLabel *linesLabel;
    QPushButton *startButton;
    QPushButton *pauseButton;
    QLabel *nextPieceLabel;
};

TetrisWindow::TetrisWindow() {
    board = new TetrisBoard(this);

    scoreLabel = new QLabel(tr("得分: 0"));
    levelLabel = new QLabel(tr("等级: 1"));
    linesLabel = new QLabel(tr("消除行数: 0"));
    nextPieceLabel = new QLabel;
    nextPieceLabel->setFrameStyle(QFrame::Box | QFrame::Raised);
    nextPieceLabel->setAlignment(Qt::AlignCenter);
    board->setNextPieceLabel(nextPieceLabel);

    startButton = new QPushButton(tr("开始"));
    startButton->setFocusPolicy(Qt::NoFocus);
    pauseButton = new QPushButton(tr("暂停"));
    pauseButton->setFocusPolicy(Qt::NoFocus);

    connect(startButton, &QPushButton::clicked, board, &TetrisBoard::start);
    connect(pauseButton, &QPushButton::clicked, board, &TetrisBoard::pause);
    connect(board, &TetrisBoard::scoreChanged, this, &TetrisWindow::updateScore);
    connect(board, &TetrisBoard::levelChanged, this, &TetrisWindow::updateLevel);
    connect(board, &TetrisBoard::linesRemovedChanged, this, &TetrisWindow::updateLines);

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(board);

    QVBoxLayout *sideLayout = new QVBoxLayout;
    sideLayout->addWidget(scoreLabel);
    sideLayout->addWidget(levelLabel);
    sideLayout->addWidget(linesLabel);
    sideLayout->addWidget(nextPieceLabel);
    sideLayout->addWidget(startButton);
    sideLayout->addWidget(pauseButton);
    sideLayout->addStretch(1);

    layout->addLayout(sideLayout);

    QWidget *centralWidget = new QWidget;
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    setWindowTitle(tr("俄罗斯方块"));
    resize(500, 400);
}

void TetrisWindow::startGame() {
    board->start();
    startButton->setEnabled(false);
}

void TetrisWindow::pauseGame() {
    board->pause();
}

void TetrisWindow::updateScore(int score) {
    scoreLabel->setText(tr("得分: %1").arg(score));
}

void TetrisWindow::updateLevel(int level) {
    levelLabel->setText(tr("等级: %1").arg(level));
}

void TetrisWindow::updateLines(int lines) {
    linesLabel->setText(tr("消除行数: %1").arg(lines));
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    TetrisWindow window;
    window.show();
    return app.exec();
}

#include "main.moc"